package JET.physics;

import JET.Centers.ControlCenter;
import JET.UtilPanels.Trees.SceneTree;
import JET.geom.Vec2d;
import JET.physics.interfaces.SceneTreeNode;

/**
 *
 * @author 057Ry
 */
public final class Mass implements SceneTreeNode
{
    static int id=0;
    int mid;

    public double mass;        // masa
    private double invMass;
    
    private final Vec2d position;     // wektor polozenia masy punktowej
    private final Vec2d lastPosition;
    private final Vec2d force;        // wektor dzialajacej na mase sily
    private final Vec2d velocity;     // wektor predkosci masy puntkowej
    
    private final Vec2d dr;           // wektor zmiany polozenia w jednym kroku czasowym
    private final Vec2d dv;           // wektor zmiany predkosci w jednym kroku czasowym
    private final Vec2d a;            // wektor przyspieszenia na poczatku danego kroku czasowego
    
    private boolean moveable;   // czy masa punktowa moze zmieniac swoje polozenie
    private boolean wasMoveable;

    public String name = "";

    public Mass(double x, double y, double mass)
    {
        mid = id++;
        name = "Mass_"+mid;

        this.position = new Vec2d(x,y);
        this.lastPosition = new Vec2d(x,y);
        this.force = new Vec2d();
        this.velocity = new Vec2d();
        this.dr = new Vec2d();
        this.dv = new Vec2d();
        this.a = new Vec2d();
        
        setMass(mass);
        this.moveable = true;

        // Set Scene Tree Nodes stuff
        positionTreeNode = new SceneTree.VecTreeNode(position, "pos");
        velocityTreeNode = new SceneTree.VecTreeNode(velocity, "vel");
        forceTreeNode = new SceneTree.VecTreeNode(force, "force");
    }

    public Mass(Mass mass)
    {
        mid = id++;
        name = "Mass_"+mid;
        
        this.position = new Vec2d(mass.getPosition());
        this.lastPosition = new Vec2d(mass.getLastPosition());
        this.force = new Vec2d(mass.getForce());
        this.velocity = new Vec2d(mass.getVelocity());
        this.dr = new Vec2d();
        this.dv = new Vec2d();
        this.a = new Vec2d();

        setMass(mass.getMass());
        this.moveable = mass.isMoveable();

        // Set Scene Tree Nodes stuff
        positionTreeNode = new SceneTree.VecTreeNode(position, "pos");
        velocityTreeNode = new SceneTree.VecTreeNode(velocity, "vel");
        forceTreeNode = new SceneTree.VecTreeNode(force, "force");
    }

    public void become(Mass mass)
    {
        this.name = mass.name;
        this.position.become(mass.getPosition());
        this.lastPosition.become(mass.getLastPosition());
        this.force.become(mass.getForce());
        this.velocity.become(mass.getVelocity());
        this.dr.become(Vec2d.zero);
        this.dv.become(Vec2d.zero);
        this.a.become(Vec2d.zero);

        setMass(mass.getMass());
        this.moveable = mass.isMoveable();

        // Set Scene Tree Nodes stuff
        positionTreeNode = new SceneTree.VecTreeNode(position, "pos");
        velocityTreeNode = new SceneTree.VecTreeNode(velocity, "vel");
        forceTreeNode = new SceneTree.VecTreeNode(force, "force");
    }

    public String getName() {
        return name;
    }
    public void setName(String n) {
        name = n;
    }

    public Vec2d getPosition() {
        return position;
    }
    public synchronized void setPosition(Vec2d vec) {
        position.become( vec );
    }
    public synchronized void setLastPosition(Vec2d lp)
    {
        this.lastPosition.become(lp);
    }
    public Vec2d getLastPosition() {
        return lastPosition;
    }
    
    public synchronized void setMoveable(boolean moveable) {
        this.moveable = moveable;
    }
    public boolean isMoveable() {
        return moveable;
    }
    public synchronized void setWasMoveable(boolean wasMoveable) {
        this.wasMoveable = wasMoveable;
    }
    public boolean wasMoveable() {
        return wasMoveable;
    }
    public Vec2d getForce() {
        return force;
    }
    public double getMass() {
        return mass;
    }
    public Vec2d getVelocity() {
        return velocity;
    }
    public synchronized void setForce(Vec2d force) {
        this.force.become( force ); 
    }
    public synchronized void setMass(double mass) {
        this.mass = mass;
        this.invMass = 1./mass;
    }
    public synchronized void setVelocity(Vec2d velocity) {
        this.velocity.become( velocity );
    }

    public synchronized void applyGravityForce(double value)
    {
        Vec2d gravityForce = new Vec2d(0, -value);
        gravityForce.scale( mass );
        
        this.force.add( gravityForce );
    }
    public synchronized void applyGlobalFriction(double gF )
    {
        getVelocity().scale( gF );
    }

    public synchronized void moveBy(Vec2d trace) {
        getPosition().add( trace );
    }

    public synchronized void simulate(double dt)
    {
        if ( moveable ) // jezeli masa moze sie poruszac to jedziem z koksem ...
        {
            // Calkowanie rownania ruchu masy puntkowej metodą Eulera
            // wzor ogolny : 
            //                  r' = r + ((v + f/m * dt) * dt)
            //
            // nastepne polozenie masy (r', r to ostatnie polozenie) po czasie dt
            //      r' = r + dr
            // zmiana polozenia dr w tym kroku czasowym
            //      dr = V' * dt
            // nowa predkosc masy (v', v to ostania predkosc) czyli predkosc na poczatku tego kroku czasowego
            //      v' = v + dv
            // zmiana predkosci masy dv w tym kroku czasowym
            //      dv = a * dt
            // przyspieszenie masy w tym kroku czasowym
            //      a = f/m
            
            // Powyzsza teoria zastosowana w praktyce. Lecimy "od tylu" z powyzszymi rownaniami ...
            // 1 - obliczamy przyspieszenie na poczatku tego kroku na podstawie aktualnie dzialajacej sily i masy
            a.become( force );
            a.scale( 1./mass );

            // 2 - obliczamy zmiane predkosci w tym kroku czasowym
            dv.become( a );
            dv.scale( dt );

            // 3 - uaktualniamy predkosc masy, zmieniajac ja o wektor dv
            velocity.add( dv );

            // 4 - obliczamy zmiane polozenia masy punktowej na podstawie aktualnej predkosci i kroku czasowego
            dr.become( velocity );
            dr.scale( dt );

            // 5 - przesuwamy mase o aktualna zmiane polozenia...
            lastPosition.become( position );
            position.add( dr );


            ////////////// VERLET ///////////////
//            double drag = 0.005;
//            a.become(force);
//            a.scale(invMass);
//
//            // dv as temp new position
//            // dr as temp last position
//            dv.become(position);
//            dv.scale(2-drag);
//
//            dr.become(lastPosition);
//            dr.scale(1-drag);
//
//            a.scale(dt*dt);
//
//            lastPosition.become(position);
//            position.become(dv);
//            position.sub(dr);
//            position.add(a);
        }
    }
    public boolean hasMoved() {
        if ( position.equals( lastPosition ) )
            return false;
        return true;
    }

    public Vec2d getMoveVector() {
        Vec2d r = new Vec2d( getPosition() );
        r.sub( getLastPosition() );
        return r;
    }
    public String toString() {
        return name;
    }

    @Override
    public Mass clone() {
        return new Mass(this);
    }

    ////////////////////////////////////////////////////////////////////////////
    //                S C E N E   T R E E   I N T E R F A C E                 //
    ////////////////////////////////////////////////////////////////////////////

    public NameTreeNode nameTreeNode = new NameTreeNode();
    public MassTreeNode massTreeNode = new MassTreeNode();
    public SceneTree.VecTreeNode positionTreeNode;// = new VecTreeNode(position, "pos");
    public SceneTree.VecTreeNode velocityTreeNode;// = new VecTreeNode(velocity, "vel");
    public SceneTree.VecTreeNode forceTreeNode;// = new VecTreeNode(force, "force");
    public MoveableTreeNode moveableTreeNode = new MoveableTreeNode();
    
    public SceneTreeNode getChild(int index) {
        switch (index) {
            case 0 : return nameTreeNode;
            case 1 : return massTreeNode;
            case 2 : return positionTreeNode;
            case 3 : return velocityTreeNode;
            case 4 : return forceTreeNode;
            case 5 : return moveableTreeNode;
        }

        return null;
    }

    public int getChildCount() {
        return 6;
    }

    public boolean isLeaf() {
        return false;
    }

    public int getIndexOfChild(Object child) {
        if      (child instanceof NameTreeNode) return 0;
        else if (child instanceof MassTreeNode) return 1;
        else if (child instanceof SceneTree.VecTreeNode && ((SceneTree.VecTreeNode)child).getVecNodeName().equals("pos")) return 2;
        else if (child instanceof SceneTree.VecTreeNode && ((SceneTree.VecTreeNode)child).getVecNodeName().equals("vel")) return 3;
        else if (child instanceof SceneTree.VecTreeNode && ((SceneTree.VecTreeNode)child).getVecNodeName().equals("force")) return 4;
        else if (child instanceof MoveableTreeNode) return 5;
        else
            return -1;
    }

    public void resetNodeValue(Object o) {}

    // Nodes Internal Classes

    public class NameTreeNode implements SceneTreeNode {
        public SceneTreeNode getChild(int index) {return null;}
        public int getChildCount() {return 0;}
        public boolean isLeaf() {return true;}
        public int getIndexOfChild(Object child) {return -1;}

        public void resetNodeValue(Object o) {
            name = o.toString();
        }
        
        public String toString() {
            return "name: '"+name+"'";
        }
    }

    public class MassTreeNode implements SceneTreeNode {
        public SceneTreeNode getChild(int index) {return null;}
        public int getChildCount() {return 0;}
        public boolean isLeaf() {return true;}
        public int getIndexOfChild(Object child) {return -1;}

        public void resetNodeValue(Object o) {
            try {
                setMass(Double.parseDouble(o.toString()));
                ControlCenter.getInstance().GUIC.repaintAllSceneTabs();
            } catch(NumberFormatException nfe) {}
        }

        public String toString() {
            return "mass: "+mass;
        }
    }

    public class MoveableTreeNode implements SceneTreeNode {
        public SceneTreeNode getChild(int index) {return null;}
        public int getChildCount() {return 0;}
        public boolean isLeaf() {return true;}
        public int getIndexOfChild(Object child) {return -1;}

        public void resetNodeValue(Object o) {
            moveable = Boolean.parseBoolean(o.toString());
            ControlCenter.getInstance().GUIC.repaintAllSceneTabs();
        }

        public String toString() {
            return "moveable: "+moveable;
        }
    }
}
